package top.z.work.framework.web.handler;

import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.map.MapUtil;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.ValidationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.resource.NoResourceFoundException;
import top.z.work.framework.common.exception.ServiceException;
import top.z.work.framework.common.pojo.R;
import top.z.work.framework.common.util.JsonUtil;
import top.z.work.framework.common.util.ServletUtil;
import top.z.work.module.system.api.logger.SystemLoggerErrorApi;
import top.z.work.module.system.api.logger.dto.SystemLoggerErrorDto;

import java.time.LocalDateTime;
import java.util.Map;

import static top.z.work.framework.common.exception.GlobalErrorCode.*;

/**
 * 全局异常处理器
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    @Resource
    SystemLoggerErrorApi systemLoggerErrorApi;

    /**
     * 处理所有异常，主要是提供给 Filter 使用
     * 因为 Filter 不走 SpringMVC 的流程，但是我们又需要兜底处理异常，所以这里提供一个全量的异常处理过程，保持逻辑统一。
     *
     * @param request 请求
     * @param ex      异常
     * @return 通用返回
     */
    public R<?> allExceptionHandler(HttpServletRequest request, Throwable ex) {
        if (ex instanceof MissingServletRequestParameterException) {
            return missingServletRequestParameterExceptionHandler((MissingServletRequestParameterException) ex);
        }
        if (ex instanceof MethodArgumentTypeMismatchException) {
            return methodArgumentTypeMismatchExceptionHandler((MethodArgumentTypeMismatchException) ex);
        }
        if (ex instanceof MethodArgumentNotValidException) {
            return methodArgumentNotValidExceptionHandler((MethodArgumentNotValidException) ex);
        }
        if (ex instanceof BindException) {
            return bindExceptionHandler((BindException) ex);
        }
        if (ex instanceof ConstraintViolationException) {
            return constraintViolationExceptionHandler((ConstraintViolationException) ex);
        }
        if (ex instanceof ValidationException) {
            return validationExceptionHandler((ValidationException) ex);
        }
        if (ex instanceof NoHandlerFoundException) {
            return noHandlerFoundExceptionHandler((NoHandlerFoundException) ex);
        }
        if (ex instanceof HttpRequestMethodNotSupportedException) {
            return httpRequestMethodNotSupportedExceptionHandler((HttpRequestMethodNotSupportedException) ex);
        }
        if (ex instanceof ServiceException) {
            return serviceExceptionHandler((ServiceException) ex);
        }
        if (ex instanceof NotLoginException) {
            return notLoginExceptionHandler((NotLoginException) ex);
        }
        return defaultExceptionHandler(request, ex);
    }

    /**
     * 处理 SpringMVC 请求参数缺失
     * 例如说，接口上设置了 @RequestParam("xx") 参数，结果并未传递 xx 参数
     */
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public R<?> missingServletRequestParameterExceptionHandler(MissingServletRequestParameterException ex) {
        log.error("[missingServletRequestParameterExceptionHandler]", ex);
        return R.result(BAD_REQUEST.code(), String.format("请求参数缺失:%s", ex.getParameterName()), null);
    }

    /**
     * 处理 SpringMVC 请求参数类型错误
     * <p>
     * 例如说，接口上设置了 @RequestParam("xx") 参数为 Integer，结果传递 xx 参数类型为 String
     */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public R<?> methodArgumentTypeMismatchExceptionHandler(MethodArgumentTypeMismatchException ex) {
        log.error("[methodArgumentTypeMismatchExceptionHandler]", ex);
        return R.result(BAD_REQUEST.code(), String.format("请求参数类型错误:%s", ex.getMessage()), null);
    }

    /**
     * 处理 SpringMVC 参数校验不正确
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public R<?> methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException ex) {
        log.error("[methodArgumentNotValidExceptionHandler]", ex);
        FieldError fieldError = ex.getBindingResult().getFieldError();
        assert fieldError != null; // 断言，避免告警
        return R.result(BAD_REQUEST.code(), String.format("请求参数不正确:%s", fieldError.getDefaultMessage()), null);
    }

    /**
     * 处理 SpringMVC 参数绑定不正确，本质上也是通过 Validator 校验
     */
    @ExceptionHandler(BindException.class)
    public R<?> bindExceptionHandler(BindException ex) {
        log.error("[bindExceptionHandler]", ex);
        FieldError fieldError = ex.getFieldError();
        assert fieldError != null; // 断言，避免告警
        return R.result(BAD_REQUEST.code(), String.format("请求参数不正确:%s", fieldError.getDefaultMessage()), null);
    }

    /**
     * 处理 Validator 校验不通过产生的异常
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public R<?> constraintViolationExceptionHandler(ConstraintViolationException ex) {
        log.error("[constraintViolationExceptionHandler]", ex);
        ConstraintViolation<?> constraintViolation = ex.getConstraintViolations().iterator().next();
        return R.result(BAD_REQUEST.code(), String.format("请求参数不正确:%s", constraintViolation.getMessage()), null);
    }

    /**
     * 处理 ValidationException 异常
     */
    @ExceptionHandler(ValidationException.class)
    public R<?> validationExceptionHandler(ValidationException ex) {
        log.error("[validationExceptionHandler]", ex);
        return R.result(BAD_REQUEST.code(), BAD_REQUEST.message(), null);
    }

    /**
     * 处理 SpringMVC 请求地址不存在
     * <p>
     * 注意，它需要设置如下两个配置项：
     * 1. spring.mvc.throw-exception-if-no-handler-found 为 true
     * 2. spring.mvc.static-path-pattern 为 /statics/**
     */
    @ExceptionHandler(NoHandlerFoundException.class)
    public R<?> noHandlerFoundExceptionHandler(NoHandlerFoundException ex) {
        log.error("[noHandlerFoundExceptionHandler]", ex);
        return R.result(NOT_FOUND.code(), String.format("请求地址不存在:%s", ex.getRequestURL()), null);
    }

    /**
     * 处理 SpringMVC 请求方法不正确
     * <p>
     * 例如说，A 接口的方法为 GET 方式，结果请求方法为 POST 方式，导致不匹配
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public R<?> httpRequestMethodNotSupportedExceptionHandler(HttpRequestMethodNotSupportedException ex) {
        log.error("[httpRequestMethodNotSupportedExceptionHandler]", ex);
        return R.result(METHOD_NOT_ALLOWED.code(), String.format("请求方法不正确:%s", ex.getMessage()), null);
    }

    /**
     * 处理业务异常 ServiceException
     */
    @ExceptionHandler(ServiceException.class)
    public R<?> serviceExceptionHandler(ServiceException ex) {
        log.error("[serviceExceptionHandler][业务异常: {}]", ex.getMessage());
        return R.result(ex.getCode(), ex.getMessage(), null);
    }

    /**
     * 会话未能通过登录认证校验 NotLoginException
     */
    @ExceptionHandler(NotLoginException.class)
    public R<?> notLoginExceptionHandler(NotLoginException ex) {
        log.error("[notLoginExceptionHandler][会话未能通过登录认证校验: {}]", ex.getMessage());
        return R.result(UNAUTHORIZED.code(), UNAUTHORIZED.message(), null);
    }

    /**
     * 资源不存在异常
     */
    @ExceptionHandler(NoResourceFoundException.class)
    public R<?> NoResourceFoundExceptionHandler(HttpServletRequest req, Throwable ex) {
        log.error("[NoResourceFoundException]", ex);
        // 插入异常日志
        this.createExceptionLog(req, ex);
        // 返回 ERROR R
        return R.result(NOT_FOUND.code(), NOT_FOUND.message(), null);
    }

    /**
     * 处理系统异常, 兜底处理所有的一切
     */
    @ExceptionHandler(Exception.class)
    public R<?> defaultExceptionHandler(HttpServletRequest req, Throwable ex) {
        log.error("[defaultExceptionHandler]", ex);
        // 插入异常日志
        this.createExceptionLog(req, ex);
        // 返回 ERROR R
        return R.result(INTERNAL_SERVER_ERROR.code(), INTERNAL_SERVER_ERROR.message(), null);
    }

    private void createExceptionLog(HttpServletRequest request, Throwable e) {
        // 插入错误日志
        SystemLoggerErrorDto loggerErrorDto = new SystemLoggerErrorDto();
        try {
            // 初始化
            buildExceptionLog(loggerErrorDto, request, e);
            // 执行插入
            systemLoggerErrorApi.createLoggerError(loggerErrorDto);
        } catch (Throwable th) {
            log.error("[createLoggerError][url({}) log({}) 发生异常]", request.getRequestURI(), JsonUtil.toJsonString(loggerErrorDto), th);
        }
    }

    private void buildExceptionLog(SystemLoggerErrorDto loggerErrorDto, HttpServletRequest request, Throwable e) {
        Map<String, Object> requestParameter = MapUtil.<String, Object>builder()
                .put("query", ServletUtil.getParameter(request))
                .put("body", ServletUtil.getJsonRequestBody(request))
                .build();

        StackTraceElement[] stackTraceElements = e.getStackTrace();
        Assert.notEmpty(stackTraceElements, "异常 stackTraceElements 不能为空");
        StackTraceElement stackTraceElement = stackTraceElements[0];
        if (StpUtil.isLogin()) {
            loggerErrorDto.setUserId(Long.valueOf(StpUtil.getLoginId().toString()));
        }
        loggerErrorDto.setRequestMethod(request.getMethod());
        loggerErrorDto.setRequestUrl(request.getRequestURI());
        loggerErrorDto.setRequestParameter(JsonUtil.toJsonString(requestParameter));
        loggerErrorDto.setRequestIp(ServletUtil.getClientIP());
        loggerErrorDto.setUserAgent(ServletUtil.getUserAgent(request));
        loggerErrorDto.setExceptionTime(LocalDateTime.now());
        loggerErrorDto.setExceptionName(e.getClass().getName());
        loggerErrorDto.setExceptionMessage(ExceptionUtil.getMessage(e));
        loggerErrorDto.setExceptionRootCauseMessage(ExceptionUtil.getRootCauseMessage(e));
        loggerErrorDto.setExceptionClassName(stackTraceElement.getClassName());
        loggerErrorDto.setExceptionFileName(stackTraceElement.getFileName());
        loggerErrorDto.setExceptionMethodName(stackTraceElement.getMethodName());
        loggerErrorDto.setExceptionLineNumber(stackTraceElement.getLineNumber());
    }
}
